﻿/*
 * Copyright 2005-2011 Edward L. Stauff.                  Contact: <EdStauff@gmail.com>.
 * 
 * This file, "TimeSpanParser.cs", is part of the "Stauffware.Common.Time" class library.
 * "Stauffware.Common.Time" is free software: you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser Public License as published by the 
 * Free Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * "Stauffware.Common.Time" is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with 
 * the "Stauffware.Common.Time" sources.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Globalization;
using Stauffware.Common.Core;

namespace Stauffware.Common.Time
{
	/// <summary>
	/// Options for parsing timespans.
	/// </summary>
	[Flags]
	public enum TimeSpanParseOptions : long
	{
		/// <summary>
		/// No options.
		/// </summary>
		None = 0,

		#region ParseOptions compatibility

		/// <summary>
		/// Allow leading whitespace.  (Equal to ParseOptions.AllowLeadingWhite.)
		/// </summary>
		AllowLeadingWhite	= ParseOptions.AllowLeadingWhite,

		/// <summary>
		/// Allow trailing whitespace.  (Equal to ParseOptions.AllowTrailingWhite.)
		/// </summary>
		AllowTrailingWhite	= ParseOptions.AllowTrailingWhite,

		/// <summary>
		/// Allow inner whitespace (but never between digits or time separator).
		/// (Equal to ParseOptions.AllowInnerWhite.)
		/// </summary>
		AllowInnerWhite		= ParseOptions.AllowInnerWhite,

		/// <summary>
		/// Equivalent to combining AllowLeadingWhite, AllowInnerWhite, and AllowTrailingWhite.
		/// (Equal to ParseOptions.AllowWhiteSpace.)
		/// </summary>
		AllowWhiteSpace		= ParseOptions.AllowWhiteSpace,

		/// <summary>
		/// Forces parsing of the entire input string.
		/// Without this option, extraneous characters following a valid time will
		/// terminate the parsing, but not cause an error.
		/// (Equal to ParseOptions.ParseEntireInput.)
		/// </summary>
		ParseEntireInput	= ParseOptions.ParseEntireInput,

		#endregion
	}

	/// <summary>
	/// Represents one or more units of time.
	/// </summary>
	[Flags()]
	public enum TimeUnits
	{
		/// <summary></summary>
		Millisecond = 0x01,
		/// <summary></summary>
		Seconds = 0x02,
		/// <summary></summary>
		Minutes = 0x04,
		/// <summary></summary>
		Hours = 0x08,
		/// <summary></summary>
		Days = 0x10,
		/// <summary></summary>
		Weeks = 0x20,
		/// <summary></summary>
		Years = 0x80,
		/// <summary></summary>
		None = 0x00,
		/// <summary></summary>
		Time = Hours | Minutes | Seconds,
		/// <summary></summary>
		All = 0xFF
	}

	//#################################################################################
	/// Extension methods

	public static class TimeSpanParseOptions_Extensions
	{
		/// <summary>
		/// Converts a ParseOptions value to a TimeSpanParseOptions value.
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static TimeSpanParseOptions ToTimeSpanParseOptions (this ParseOptions o)
		{
			return (TimeSpanParseOptions) o;
		}
	}

	//#################################################################################
	/// <summary>
	/// A parser for parsing a time span from a string into a TimeSpan.
	/// </summary>
	/// <remarks>
	/// Syntax:
	///		timespan	:=	["+" | "-"] unsignedTS
	///		unsignedTS	:=	"0" | [days] time | float unit
	///		days		:=	int ("d" | "da" | "day" | "days")
	///		time		:=	digit [digit] ":" digit [digit] ":" digit [digit] ["." digit...]
	///		int			:=	{ unsigned integer }
	///		float		:=	{ unsigned floating-point number }
	///		unit		:=	{ any valid time unit name or abbreviation, or an unambiguous prefix thereof }
	/// </remarks>

	public class TimeSpanParser
	{
		/// <summary>
		/// What to use when there isn't a TimeSpanParseOptions parameter.
		/// </summary>
		public const TimeSpanParseOptions DefaultParseOptions = 
			TimeSpanParseOptions.AllowWhiteSpace | TimeSpanParseOptions.ParseEntireInput;

		internal readonly TimeSpanParseOptions Options;

		private IFormatProvider FmtProvider;

		//-----------------------------------------------------------------------------
		// class constructor

		static TimeSpanParser ()
		{


		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Constructs a parser that uses DefaultParseOptions.
		/// </summary>
		public TimeSpanParser ( ) : this(DefaultParseOptions, null) { }

		/// <summary>
		/// Constructs a parser.
		/// </summary>
		/// <param name="options">parsing options</param>
		public TimeSpanParser (TimeSpanParseOptions options) : this(options, null) { }

		/// <summary>
		/// Constructs a parser that uses DefaultParseOptions.
		/// </summary>
		/// <param name="ifp">formatting info, or null</param>
		public TimeSpanParser (IFormatProvider ifp) : this(DefaultParseOptions, ifp) { }

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Constructs a parser.
		/// </summary>
		/// <param name="options">parsing options</param>
		/// <param name="ifp">format provider</param>

		public TimeSpanParser (TimeSpanParseOptions options, IFormatProvider ifp)
		{
			this.Options = options;
			this.FmtProvider = ifp;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Parses a time span.  
		/// Should never throw an exception except for failed assertions (programming errors).
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <returns>
		///		The result of the parse attempt, whether successful or not; will never return null.
		/// </returns>

		public TimeSpanParse Parse (string input)
		{
			// CRAPPY TEMPORARY IMPLEMENTATION

			TimeSpanParse parse = new TimeSpanParse();

			parse._parsedText = input;

			if (this.Options.HasFlag(TimeSpanParseOptions.AllowLeadingWhite))
				input = input.TrimStart();
			if (this.Options.HasFlag(TimeSpanParseOptions.AllowTrailingWhite))
				input = input.TrimEnd();

			if (input == "")
				return parse;	// empty: no value, no error

			if (input == "0")
			{
				parse._value = TimeSpan.Zero;
				return parse;
			}

			try
			{
				parse._value = TimeSpan.Parse(input, this.FmtProvider);
			}
			catch (FormatException e)
			{
				parse._errorMessage = e.Message;
			}
			catch (OverflowException e)
			{
				parse._errorMessage = e.Message;
			}

			return parse;
		}

		//-----------------------------------------------------------------------------
	}

	//#################################################################################
	/// <summary>
	/// Encapsulates the result of trying to parse a time span from a string into a TimeSpan,
	/// whether successful or unsuccessful.
	/// </summary>

	public class TimeSpanParse : ValueParse<TimeSpan>
	{
	}

	//#################################################################################
}
